通过对整个 ReactDOM.render 所触发的渲染链路进行了分析和串联,我们已经把 Fiber 架构在实现层面的大部分要点都过了一遍。刚讲过的这部分知识,一方面相对来说复杂度比较高,需要一些耐心反复地理解和消化;另一方面,本讲接下来要讲解的内容,也和它存在着较强的依赖关系,因此对这些前置知识的把握就显得尤为重要。
下面我说几个函数,帮你检验一下自己的学习效果:
performSyncWorkOnRootworkLoopSyncperformUnitOfWorkbeginWorkcompleteWorkcompleteUnitOfWorkreconcileChildFibers
本讲我将带你去认识 Fiber 架构最迷人的那一面——Concurrent 模式(异步渲染)下的“时间切片”和“优先级”实现。
# current 树 与 workInProgress 树:“双缓冲”模式在 Fiber 架构下的实现
# 什么是“双缓冲”模式
“双缓冲”模式其实是一种在游戏领域由来已久的经典设计模式。为了帮助你快速理解它,这里我先举一个生活中的例子:假如你去看一场总时长只有 1 个小时的话剧,这场话剧中场不休息,需要不间断地演出。
按照剧情的需求,半个小时处需要一次转场。所谓转场,就是说话剧舞台的灯光、布景、氛围等全部要切换到另一种风格里去。在不中断演出的情况下,想要实现转场,怎么办呢?场务工作做得再快,也要十几二十分钟,这对一场时长 1 小时的话剧来说,实在太漫长了。观众也无法接受这样的剧情“卡顿”体验。
有一种解法,那就是准备两个舞台来做这场戏,当第一个舞台处于使用中时,第二个舞台的布局已经完成。这样当第一个舞台的表演结束时,只需要把第一个舞台的灯光灭掉,第二个舞台的灯光亮起,就可以做到剧情的无缝衔接了。
事实上,在真实的话剧中,我们也确实常常看到这样的画面——演员从舞台的左侧走到了右侧,灯光一切换,就从卧室(左侧舞台)走到了公园(右侧舞台);又从公园(右侧舞台)走到了办公室(左侧舞台)。左侧舞台的布景从卧室变成了办公室,这个过程正是在演员利用右侧舞台表演时完成的。
在这个过程中,我们可以认为,左侧舞台和右侧舞台分别是两套缓冲数据,而呈现在观众眼前的连贯画面,就是不同的缓冲数据交替被读取后的结果。
在计算机图形领域,通过让图形硬件交替读取两套缓冲数据,可以实现画面的无缝切换,减少视觉效果上的抖动甚至卡顿。而在 React 中,双缓冲模式的主要利好,则是能够帮我们较大限度地实现 Fiber 节点的复用,从而减少性能方面的开销。
# current 树与 workInProgress 树之间是如何“相互利用”的
在 React 中,current 树与 workInProgress 树,两棵树可以对标“双缓冲”模式下的两套缓冲数据:当 current 树呈现在用户眼前时,所有的更新都会由 workInProgress 树来承接。workInProgress 树将会在用户看不到的地方(内存里)悄悄地完成所有改变,直到“灯光”打到它身上,也就是 current 指针指向它的时候,此时就意味着 commit 阶段已经执行完毕,workInProgress 树变成了那棵呈现在界面上的 current 树。
接下来我将用一个 Demo,带你切身感受一把 workInProgress 树和 current 树“相互利用”的过程。代码如下:
import { useState } from 'react';
function App() {
const [state, setState] = useState(0)
return (
<div className="App">
<div onClick={() => { setState(state + 1) }} className="container">
<p style={{ width: 128, textAlign: 'center' }}>
{state}
</p>
</div>
</div>
);
}
export default App;
这个组件挂载后呈现出的界面很简单,就是一个数字 0,如下图所示:

# 挂载后的 Fiber 树
关于 Fiber 树的构建过程,前面已经详细讲解过,这里不再重复。下面我直接为你展示挂载时的 render 阶段结束后,commit 执行前,两棵 Fiber 树的形态,如下图所示:

待 commit 阶段完成后,右侧的 workInProgress 树对应的 DOM 树就被真正渲染到了页面上,此时 current 指针会指向 workInProgress 树:

由于挂载是一个从无到有的过程,在这个过程中我们是在不断地创建新节点,因此还谈不上什么“节点复用”。节点复用要到更新过程中
